using System;
using System.Collections.Generic;
using System.Text;
using MacomberMap.Common.Types;
using System.Xml;
using System.Data.Common;
using MacomberMap.Common.Internals;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.Data;
using MacomberMap.Common.Logging;
using MacomberMap.Common.Events;
using System.Windows.Forms;
using System.Drawing;

namespace MacomberMap.Common.Components
{

    /// <summary>
    /// This class is the base for all of our elements that will be parsed within MM.
    /// </summary>
    [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.Both)]    
    public class MM_Element : MM_Serializable, IComparable<MM_Element>, IEquatable<MM_Element>
    {
        #region Variable declarations
        /// <summary>The name of the element</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.Both)]
        public string Name;

        /// <summary>The CIM transmission element identifier</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.Index)]
        public UInt64 TEID;

        /// <summary>The KV level of the element, if any.</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.Both)]
        public MM_KVLevel KVLevel;

        /// <summary>The substation in which the element resides (null for substations and lines)</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.Both)]
        public MM_Substation Substation;

        /// <summary>The name of the substation</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.ElementCollection)]
        public String SubName
        {
            get
            {
                if (Substation == null)
                    return null;
                else
                    return Substation.Name;
            }
        }

        /// <summary>The element type</summary> 
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.ElementCollection)]
        public MM_Element_Type ElemType;

        /// <summary>The collection of data for this element</summary>
        public MM_Data_Collection Data = new MM_Data_Collection();

        /// <summary>The owner of the company</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.TypeCollection)]
        public MM_Company Owner;

        /// <summary>The operator of the company</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.TypeCollection)]
        public MM_Company Operator;

        /// <summary>A randomizer for locating unique TEIDs</summary>
        private static Random TEIDRandomizer = new Random();
        #endregion

        #region Initialization
        /// <summary>
        /// Parameter-free constructor
        /// </summary>
        public MM_Element()
        {
        }

        /// <summary>
        /// Initialize a new CIM element based on an XML element
        /// </summary>
        /// <param name="ElementSource">The XML element containing the TEID, name, owner and operator for the element in question</param>
        public MM_Element(XmlElement ElementSource)
            : base(ElementSource)
        {
            this.ElemType = MM_Overall_Display.FindElementType(ElementSource.Name);

        }

        /// <summary>
        /// Retrieve a unique TEID for an element
        /// </summary>
        /// <returns></returns>
        public UInt64 UniqueTEID()
        {
            UInt64 TEID = 0;
            //Randomly determine the TEID
            while (TEID == 0 || MM_Repository.TEIDs.ContainsKey(TEID) || MM_Repository.MismatchedTEIDs.ContainsKey(TEID))
            {
                byte[] outBytes = new byte[8];
                MM_Element.TEIDRandomizer.NextBytes(outBytes);
                TEID = BitConverter.ToUInt64(outBytes, 0);
            }
            return TEID;

        }

        /// <summary>
        /// Initialize a new CIM element based on a data reader element
        /// </summary>
        /// <param name="ElementSource">The data reader containing the TEID, name, owner and operator for the element in question</param>        
        public MM_Element(DbDataReader ElementSource): base(ElementSource)
        {
            for (int Col = 0; Col < ElementSource.FieldCount; Col++)
                if (ElementSource[Col] is DBNull == false)
                    switch (ElementSource.GetName(Col).ToUpper())
                    {
                        case "ELEMNAME":
                        case "NAME":
                            this.Name = (String)ElementSource[Col]; break;
                        case "TEID": this.TEID = Convert.ToUInt32(ElementSource[Col]); break;
                        case "OWNER":
                            UInt64 Owner = Convert.ToUInt64(ElementSource[Col]);
                            if (MM_Repository.TEIDs.ContainsKey(Owner) && MM_Repository.TEIDs[Owner] is MM_Company)
                                this.Owner = (MM_Company)MM_Repository.TEIDs[Owner];
                            else
                                MM_Log.LogEvent(new MM_Event_ItemNotFound(this, "Owner", typeof(MM_Repository).GetProperty("TEIDs"), Owner));
                            break;

                        case "OPERATOR":
                            UInt64 Operator = Convert.ToUInt64(ElementSource[Col]);
                            if (MM_Repository.TEIDs.ContainsKey(Operator) && MM_Repository.TEIDs[Operator] is MM_Company)
                                this.Operator = (MM_Company)MM_Repository.TEIDs[Operator];
                            else
                                MM_Log.LogEvent(new MM_Event_ItemNotFound(this, "Operator", typeof(MM_Repository).GetProperty("TEIDs"),Operator));
                            break;
                      
                        case "SUBSTATION":
                            UInt64 Substation = Convert.ToUInt64(ElementSource[Col]);
                            if (MM_Repository.TEIDs.ContainsKey(Substation) && MM_Repository.TEIDs[Substation] is MM_Substation)
                                this.Substation = (MM_Substation)MM_Repository.TEIDs[Substation];
                            else
                                MM_Log.LogEvent(new MM_Event_ItemNotFound(this, "Substation", typeof(MM_Repository).GetProperty("TEIDs"),Substation));
                            break;
                        case "KVLEVEL": foreach (MM_KVLevel KVLevel in MM_Overall_Display.KVLevels.Values)
                                if (KVLevel.Index == Convert.ToInt32(ElementSource[Col]))
                                    this.KVLevel = KVLevel;
                            break;
                        case "TYPE":
                            if (this is MM_Unit)
                                (this as MM_Unit).UnitType = ElementSource[Col] as String;
                            else
                                foreach (MM_Element_Type ElemType in MM_Overall_Display.ElemTypes.Values)
                                    if (ElemType.Index == Convert.ToUInt32(ElementSource[Col]))
                                        this.ElemType = ElemType;
                            break;
                        case "COORDINATES":
                            if (this is MM_Line)
                            {
                                byte[] InCoordinates = (byte[])ElementSource[Col];
                                PointF[] OutCoordinates = new PointF[InCoordinates.Length / 8];
                                for (int a = 0; a < OutCoordinates.Length / 8; a++)
                                    OutCoordinates[a] = new PointF(BitConverter.ToSingle(InCoordinates, a * 8), BitConverter.ToSingle(InCoordinates, (a * 8) + 4));
                                (this as MM_Line).Coordinates = OutCoordinates;
                            }
                            break;
                    }

        }

        /// <summary>
        /// Create a new element based on type
        /// </summary>
        /// <param name="ElemType"></param>
        /// <returns></returns>
        public static MM_Element CreateElement(MM_Element_Type ElemType)
        {
            MM_Element OutElement;
            Type TargetType = MM_Type_Finder.LocateType("MM_" + ElemType, null);
            if (TargetType != null)
                OutElement = Activator.CreateInstance(TargetType) as MM_Element;
            else
                OutElement = new MM_Element();
            /*



            //Now, depending on the type, create our new element                
            if (ElemType.Name == "Transformer")
                OutElement = new MM_Transformer();
            else if (ElemType.Name == "Substation")
                OutElement = new MM_Substation();
            else if (ElemType.Name == "Unit")
                OutElement = new MM_Unit();
            else if (ElemType.Name == "Line")
                OutElement = new MM_Line();
            else if (ElemType.Name == "DCTie")
                OutElement = new MM_DCTie();
            else if (ElemType.Name == "Breaker")
                OutElement = new MM_Breaker();
            else if (ElemType.Name == "ElectricalBus")
                OutElement = new MM_ElectricalBus();
            else if (ElemType.Name == "Company")
                OutElement = new MM_Company();
            else if (ElemType.Name == "Contingency")
                OutElement = new MM_Contingency();
            else if (ElemType.Name == "PricingVector")
                OutElement = new MM_PricingVector();
            else if (ElemType.Name == "EPSMeter")
                OutElement = new MM_EPSMeter();
            else
                OutElement = new MM_Element();*/
                
            OutElement.ElemType = ElemType;            
            return OutElement;
        }

        /// <summary>
        /// Create a new element based on an data row 
        /// </summary>
        /// <param name="DbRow">The row from the database</param>
        /// <param name="ElemType">The specified element type</param>
        /// <returns></returns>
        public static MM_Element CreateElement(DbDataReader DbRow, MM_Element_Type ElemType)
        {
            MM_Element OutElement;
            //Now, depending on the type, create our new element                
            if (ElemType.Name == "Transformer")
                OutElement = new MM_Transformer(DbRow);
            else if (ElemType.Name == "Substation")
                OutElement = new MM_Substation(DbRow);
            else if (ElemType.Name == "Unit")
                OutElement = new MM_Unit(DbRow);
            else if (ElemType.Name == "Line")
                OutElement = new MM_Line(DbRow);
            else if (ElemType.Name == "DCTie")
                OutElement = new MM_DCTie(DbRow);
            else if (ElemType.Name == "ElectricalBus")
                OutElement = new MM_ElectricalBus(DbRow);
            else if (ElemType.Name == "Company")
                OutElement = new MM_Company(DbRow);
            else if (ElemType.Name == "Breaker")
                OutElement = new MM_Breaker(DbRow);
            else if (ElemType.Name == "Contingency")
                OutElement = new MM_Contingency((string)DbRow["Name"], (string)DbRow["Description"]);
            else
            {
                OutElement = new MM_Element(DbRow);
                OutElement.ElemType = ElemType;
            }
            return OutElement;
        }
        #endregion

        #region Access control
        /// <summary>
        /// Whether this substation can be displayed, given its permission levels
        /// </summary>
        public bool Permitted
        {
            get
            {
                if (this is MM_Substation)
                {
                    foreach (MM_KVLevel KVLevel in (this as MM_Substation).KVLevels)
                        if (KVLevel.Permitted && KVLevel.Name != "Other KV")
                            return true;
                    return false;
                }
                else if (this is MM_Transformer)
                {
                    foreach (MM_Element Winding in (this as MM_Transformer).Windings)
                        if (Winding.KVLevel.Permitted)
                            return true;
                    return false;
                }
                else if (this.KVLevel == null)
                    return true;
                else
                    return this.KVLevel.Permitted;
            }
        }
        #endregion

        #region Helper routines
        /// <summary>
        /// Return an easy to read identifier
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (ElemType != null)
                return ElemType.Name + " " + this.Name + " (" + this.TEID.ToString() + ")";
            else
                return GetType().Name.Substring(GetType().Name.LastIndexOf('_') + 1) + " " + this.Name + " (" + this.TEID.ToString() + ")";
        }
        #endregion

        #region IComparable<MM_Element> Members
        /// <summary>
        /// Compare two elements
        /// </summary>
        /// <param name="OtherElement">The other element to which it will be compared</param>
        /// <returns></returns>
        public int CompareTo(MM_Element OtherElement)
        {
            //First, sort by element type
            int ElemType = this.ElemType.Index.CompareTo(OtherElement.ElemType.Index);
            if (ElemType != 0)
                return ElemType;

            if (this.KVLevel != null && OtherElement.KVLevel != null)
            {
                int KVLevel = this.KVLevel.Index.CompareTo(OtherElement.KVLevel.Index);
                if (KVLevel != 0)
                    return KVLevel;
            }

            int Name = this.Name.CompareTo(OtherElement.Name);
            if (Name != 0)
                return Name;

            else if (Substation != null && OtherElement.Substation != null)
                return Substation.Name.CompareTo(OtherElement.Substation.Name);

            return 0;


        }

        #endregion

        #region Menu building
        /// <summary>
        /// Build menu items for this element
        /// </summary>
        /// <param name="MenuStrip"></param>
        public void BuildMenuItems(ContextMenuStrip MenuStrip)
        {
               

            //Depending on our element type, offer additional items as necessary
            if (this is MM_Substation)
                (this as MM_Substation).AddMenuItems(MenuStrip);
            else if (this is MM_Line)
                (this as MM_Line).AddMenuItems(MenuStrip);
            else
                MenuStrip.Items.Add(ElemType.Name + " " + this.Name);

            //If we have a KV level, show it.
            if (this.KVLevel != null)
                MenuStrip.Items.Add("  Voltage: " + KVLevel.Name);
            MenuStrip.Items.Add("  TEID: " + this.TEID.ToString("#,##0"));


            //Now, add the ownership and operatorship information
            MenuStrip.Items.Add("-");
            AddCompanyMenuItems(MenuStrip);

        }

        /// <summary>
        /// Add ownership and operatorship items to the the list
        /// </summary>
        /// <param name="MenuStrip"></param>
        private void AddCompanyMenuItems(ContextMenuStrip MenuStrip)
        {
            //TODO: Update permissions here
            if (Operator != null)
            {
                if (Operator == Owner)
                {
                    MenuStrip.Items.Add("Operator/Owner: " + Operator.Name);
                    MenuStrip.Items.Add("  Phone: " + Operator.PrimaryPhone);
                    MenuStrip.Items.Add("  DUNS: " + Operator.DUNS);
                }
                else
                {
                    MenuStrip.Items.Add("Operator: " + Operator.Name);
                    MenuStrip.Items.Add("  Phone: " + Operator.PrimaryPhone);
                    MenuStrip.Items.Add("  DUNS: " + Operator.DUNS);
                    MenuStrip.Items.Add("-");
                    MenuStrip.Items.Add("Owner: " + Owner.Name);
                    MenuStrip.Items.Add("  Phone: " + Owner.PrimaryPhone);
                    MenuStrip.Items.Add("  DUNS: " + Owner.DUNS);
                }
            }
            else
                MenuStrip.Items.Add("No ownership/operatorship information available");
        }
        #endregion

        #region IEquatable<MM_Element> Members
        /// <summary>
        /// Determine whether this element is equal to another
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        bool IEquatable<MM_Element>.Equals(MM_Element other)
        {
            if (this.GetType() != other.GetType())
                return false;
            else if (this.ElemType != other.ElemType)
                return false;
            else if (this.Substation != other.Substation)
                return false;
            else if (this.Name != other.Name)
                return false;
            else if (this.TEID != other.TEID)
                return false;
            else
                return true;
        }

        /// <summary>
        /// Determine whether an element is equal to another
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            MM_Element other = obj as MM_Element;
            if (other == null)
                return false;
            else if (this.GetType() != other.GetType())
                return false;
            else if (this.ElemType != other.ElemType)
                return false;
            else if (this.Substation != other.Substation)
                return false;
            else if (this.Name != other.Name)
                return false;
            else if (this.TEID != other.TEID)
                return false;
            else
                return true;            
        }

        /// <summary>
        /// Override the hash code to TEID
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return TEID.GetHashCode();
        }
        #endregion

    }
}
